package net.fornwall.eclipsecoder.languages;

import java.io.ByteArrayInputStream;

import net.fornwall.eclipsecoder.preferences.EclipseCoderPlugin;
import net.fornwall.eclipsecoder.stats.CodeGenerator;
import net.fornwall.eclipsecoder.stats.ProblemStatement;
import net.fornwall.eclipsecoder.util.Utilities;
import net.fornwall.eclipsecoder.views.ProblemStatementView;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;

/**
 * <p>
 * Each implementation of this class provides support for using a programming
 * language with EclipseCoder.
 * </p>
 * 
 * <p>
 * LanguageSupport has two separate tasks:
 * </p>
 * <ol>
 * <li>Support generation of test cases and formatting source code.</li>
 * <li>Support the creation of appropriate Eclipse projects.</li>
 * </ol>
 * 
 * <p>
 * Instances of subclasses should be prepared to be reused (used in cache of
 * LanguageSupportFactory).
 * </p>
 */
public abstract class LanguageSupport {

	protected CodeGenerator codeGenerator;

	public final CodeGenerator getCodeGenerator() {
		return codeGenerator;
	}

	class SubmissionGetter implements Runnable {
		public String submission;

		public void run() {
			try {
				submission = getSubmission();
			} catch (Exception e) {
				// probably just file being removed
				// e.printStackTrace();
			}
		}
	}

	public static final String BROWSER_ID = "net.fornwall.eclipsecoder.browser";

	public static final String CLASSNAME_TAG = "$CLASSNAME$";

	public static final String CLASSNAME_TAG_ESCAPED = "\\$CLASSNAME\\$";

	public static final String CPP_LANGUAGE_NAME = "C++";

	public static final String CSHARP_LANGUAGE_NAME = "C#";

	public static final String DUMMYRETURN_TAG = "$DUMMYRETURN$";

	public static final String DUMMYRETURN_TAG_ESCAPED = "\\$DUMMYRETURN\\$";

	public static final String EXTENSION_SUPPORT_ID = "net.fornwall.eclipsecoder.languagesupport";

	public static final String JAVA_LANGUAGE_NAME = "Java";

	public static final String METHODNAME_TAG = "$METHODNAME$";

	public static final String METHODNAME_TAG_ESCAPED = "\\$METHODNAME\\$";

	public static final String METHODPARAMS_TAG = "$METHODPARAMS$";

	public static final String METHODPARAMS_TAG_ESCAPED = "\\$METHODPARAMS\\$";

	public static final String PREFERENCES_NODE = "/net/fornwall/eclipsecoder/codetemplate";

	public static final String RETURNTYPE_TAG = "$RETURNTYPE$";

	public static final String RETURNTYPE_TAG_ESCAPED = "\\$RETURNTYPE\\$";

	public static final String VB_LANGUAGE_NAME = "VB";

	private String savedSource = null;

	/**
	 * The file containing the solution which is to be submitted.
	 */
	private IFile sourceFile;

	protected abstract CodeGenerator createCodeGenerator(
			ProblemStatement problemStatemnt);

	/**
	 * Create a new project.
	 * 
	 * Must be called in the SWT thread.
	 * 
	 * @param problemStatement
	 *            the problem statement to create a project for
	 */
	public final void createProject(final ProblemStatement theProblemStatement) {
		try {
			problemStatement = theProblemStatement;
			codeGenerator = createCodeGenerator(problemStatement);

			IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace()
					.getRoot();
			IWorkbench workbench = PlatformUI.getWorkbench();
			IWorkbenchWindow workbenchWindow = workbench
					.getActiveWorkbenchWindow();
			IProject myProject = workspaceRoot.getProject(getProjectName());

			if (myProject.exists()) {
				sourceFile = myProject.getFile(getSolutionFileName());
				if (sourceFile.exists()) {
					Utilities.setPerspective(getPerspectiveID());
					workbenchWindow.getActivePage().showView(
							ProblemStatementView.VIEW_ID);
					workbenchWindow.getActivePage().openEditor(
							new FileEditorInput(sourceFile), getCodeEditorID());
					workbenchWindow.getShell().forceActive();
					return;
				}

				workbenchWindow.getShell().forceActive();
				if (Utilities
						.showOkCancelDialog(
								"Malformed project exists",
								"The project \""
										+ getProjectName()
										+ "\" already exists but lacks the expected source file \""
										+ getSolutionFileName()
										+ "\"!\n\nPress Ok if you want to delete the project and create a new one.")) {
					myProject.delete(true, true, null);
					myProject = workspaceRoot.getProject(getProjectName());
				} else {
					return;
				}
			}

			myProject.create(null);
			myProject.open(null);

			IFile htmlProblemStatementFile = myProject
					.getFile(theProblemStatement.getSolutionClassName()
							+ ".html");
			htmlProblemStatementFile.create(new ByteArrayInputStream(
					getHtmlDescription().getBytes()), true, null);
			// NOTE: set this property before making the new project active so
			// that the problem statement view
			// gets updated right
			IScopeContext context = new ProjectScope(myProject);
			IEclipsePreferences prefs = context
					.getNode(EclipseCoderPlugin.PLUGIN_ID);
			prefs.put(ProblemStatementView.PREFS_KEY, htmlProblemStatementFile
					.getProjectRelativePath().toPortableString());
			prefs.flush();

			sourceFile = createLanguageProject(myProject);

			Utilities.setPerspective(getPerspectiveID());
			workbenchWindow.getActivePage().showView(
					ProblemStatementView.VIEW_ID);
			workbenchWindow.getActivePage().openEditor(
					new FileEditorInput(sourceFile), getCodeEditorID());

			workbenchWindow.getShell().forceActive();
			workbenchWindow.getShell().forceFocus();
		} catch (Exception e) {
			Utilities.showException(e);
		}
	}

	/**
	 * Implementations implement this method to create the language-specific
	 * parts of a project.
	 * 
	 * @return The file containing the problem class.
	 * @throws Exception
	 */
	protected abstract IFile createLanguageProject(IProject project)
			throws Exception;

	protected IFile getSourceFile() {
		return sourceFile;
	}

	protected abstract String getCodeTemplate();

	/**
	 * Implementations should return the ID of the editor that should be used to
	 * open the problem statement source code file.
	 * 
	 * @return The appropriate editor ID.
	 */
	protected abstract String getCodeEditorID();

	/**
	 * Get the problem description in HTML form. This varies for each
	 * programming language, but different subclasses of this class does not
	 * need to reimplement this method as it takes language into account.
	 * 
	 * @return the HTML description of the problem statement
	 */
	private String getHtmlDescription() {
		try {
			// change white on black to black on white
			return problemStatement.getHtmlDescription().replaceAll(
					"bgcolor=\"#000000\"", "").replaceAll("text=\"#ffffff\"",
					"");
		} catch (Exception e) {
			// Very unlikely
			e.printStackTrace();
			return "<html><body><h1>Error rendering to HTML:</h1><pre>"
					+ e.getMessage() + "</pre></body></html>";
		}
	}

	final public String getInitialSource() {
		return (savedSource == null) ? getSolutionStub() : savedSource;
	}

	/**
	 * Implementations should return the name of the programming language that
	 * they provide support for. If the support is for one of the
	 * TopCoder-supported languages C++, C#, Java or VB the named defined by the
	 * *_LANGUAGE_NAME static fields of this interface must be used.
	 * 
	 * @return the name of the programming language that the implementation
	 *         supports.
	 */
	public abstract String getLanguageName();

	/**
	 * Implementations should return the ID of the perspective to set when
	 * working on the problem in the current language.
	 * 
	 * @return The appropriate perspective ID.
	 */
	public abstract String getPerspectiveID();

	/**
	 * Get the name of the project created for the problem statement.
	 * 
	 * @return the name of the newly created project.
	 */
	public String getProjectName() {
		return (problemStatement.getSolutionClassName() + "-" + getLanguageName())
				.toLowerCase();
	}

	protected abstract String getSolutionFileName();

	/**
	 * Get the solution stub for the problem (not including test cases).
	 * 
	 * The tags from the code template are replaced with the actual contents.
	 * 
	 * @return The solution stub for the problem.
	 */
	final private String getSolutionStub() {
		return getCodeGenerator().getSolutionStub(getCodeTemplate());
	}

	/**
	 * Return the submission which will be submitted.
	 * 
	 * @return The submission.
	 */
	protected abstract String getSubmission() throws Exception;

	public String getSubmissionOutsideThread() throws Exception {
		Display display = PlatformUI.getWorkbench().getDisplay();
		if (display.getThread().equals(Thread.currentThread())) {
			return getSubmission();
		}

		SubmissionGetter getter = new SubmissionGetter();
		display.syncExec(getter);
		return getter.submission;
	}

	private ProblemStatement problemStatement;

	/** For subclasses */
	final protected ProblemStatement getProblemStatement() {
		return problemStatement;
	}

	/**
	 * Setting this will override the generated source and use the supplied
	 * source instead unless null.
	 * 
	 * @param source
	 *            The supplied source (stored on TC server).
	 */
	public void setSavedSource(String source) {
		this.savedSource = source;
	}
}
